<template>
  <div class="flex flex-row w-full h-full">
    <div class="w-[300px] flex flex-col gap-[10px] border-r border-r-black border-solid overflow-auto">
      <AdbDevice ref="adbDevices" v-for="item in devices" :key="item.key" :info="item" :active="firstInfo === item.key"
        @changeActive="onChangeActive" @changeImgPath="onChangeImgPath(item, $event)">
      </AdbDevice>
    </div>
    <div class="flex-1 min-w-0 h-full p-4">
      <AdbDeviceController v-if="firstInfoDevice" :info="firstInfoDevice" @command="onCommand" :refreshImageDelay="10">
      </AdbDeviceController>
      <div>
        <el-button type="primary" v-if="!recording" @click="onStartRecording">开始录制</el-button>
        <el-button type="primary" v-else @click="onStopRecording">停止录制</el-button>
        <el-button type="primary" @click="onImport">导入</el-button>
      </div>
      <div>
        <el-table :data="recordsTableData" style="width: 100%" row-key="key">
          <el-table-column prop="name" label="名称" />
          <el-table-column prop="type" label="类型">
            <template #default="{ row, $index }">
              {{ row.type === 2 ? '定时任务' : '普通' }}
            </template>
          </el-table-column>
          <el-table-column prop="isLoop" label="循环执行">
            <template #default="{ row, $index }">
              {{ row.isLoop ? '是' : '否' }}
            </template>
          </el-table-column>
          <el-table-column prop="loopDelay" label="循环间隔(秒)">
            <template #default="{ row, $index }">
              <span>{{ row.loopDelay }}秒</span>
            </template>
          </el-table-column>
          <el-table-column fixed="right" label="操作" min-width="120">
            <template #default="{ row, $index }">
              <el-button v-if="!row.startRun" :disabled="hasStarted" link type="primary" size="small"
                @click="onStartRun(row, $index)">
                开始
              </el-button>
              <el-button v-if="!row.startRun" link type="primary" size="small" @click="onSetting(row, $index)">
                配置
              </el-button>
              <el-button v-else link type="primary" size="small" @click="onStopRun(row, $index)">
                停止
              </el-button>
              <el-button v-if="!row.startRun" link type="primary" size="small"
                @click="onRemoveRow(row, $index)">删除</el-button>
              <el-button v-if="!row.startRun" link type="primary" size="small"
                @click="onDownloadJson(row, $index)">下载</el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </div>
  </div>
  <el-drawer title="配置" v-model="settingDrawerVisible">
    <el-form :model="settingForm">
      <el-form-item label="名称" prop="name">
        <el-input v-model="settingForm.name" />
      </el-form-item>
      <el-form-item label="类型" prop="type">
        <el-radio-group v-model="settingForm.type">
          <el-radio :value="1">普通</el-radio>
          <el-radio :value="2">定时任务</el-radio>
        </el-radio-group>
      </el-form-item>
      <el-form-item v-if="settingForm.type === 2" label="Cron表达式" prop="cron">
        <el-input v-model="settingForm.cron" />
      </el-form-item>
      <el-form-item v-if="settingForm.type === 2" label="">
        <div class="flex flex-col">
          <div class="flex flex-row">
            <div>说明：</div>
            <div>{{ cronDescription }}</div>
          </div>
          <div class="flex flex-row">
            <div>下次执行时间：</div>
            <div>{{ cronNextTime }}</div>
          </div>
        </div>
      </el-form-item>
      <el-form-item v-if="settingForm.type === 1" label="是否循环执行" prop="isLoop">
        <el-switch v-model="settingForm.isLoop" />
      </el-form-item>
      <el-form-item v-if="settingForm.type === 1" label="循环间隔(秒)" prop="loopDelay">
        <el-input-number v-model="settingForm.loopDelay" :min="0" :max="60000" />
      </el-form-item>
    </el-form>
    <template #footer>
      <div class="w-full flex flex-row items-center gap-4 justify-center">
        <el-button @click="settingDrawerVisible = false">取消</el-button>
        <el-button type="primary" @click="onSetRow">确认</el-button>
      </div>
    </template>
  </el-drawer>
</template>
<script setup>
import { computed, onBeforeMount, onMounted, provide, ref, shallowRef, toRaw, toValue, watch, watchEffect } from 'vue';
import AdbDevice from './components/add-device.vue'
import AdbDeviceController from './components/add-device-controller.vue'
import AdbController from './components/adb-controller.vue'
import { useLocalStorageState } from 'vue-hooks-plus';
import { downloadJson, pickFile } from './utils';
import cronstrue from 'cronstrue'
import { Cron } from "croner";
import { CronExpressionParser } from 'cron-parser';
import 'cronstrue/locales/zh_CN'
import { dayjs, ElMessage } from 'element-plus';

const defaultDeviceHost = '127.0.0.1' // '192.168.1.19'
const defaultWsHost = '127.0.0.1'
// const ports = [16384, 16416] // , 16448, 16480, 16512]
const ports = [16384, 16416, 16448, 16480, 16512]
// const ports = [16384]
const adbDevices = shallowRef([])
const adbFirstDevice = shallowRef()
const moveCenter = {
  up: 'KeyW',
  down: 'KeyS',
  left: 'KeyA',
  right: 'KeyD',
  distance: 90,
  x: 140,
  y: 380
}
const keyMap = {
  'KeyJ': {
    x: 874,
    y: 428
  },
  'KeyO': {
    x: 810,
    y: 280
  }
}

const firstInfo = shallowRef()
// const devices = ref(ports.map(item => {
//   return {
//     key: `${defaultDeviceHost}:${item}`,
//     type: 'ip',
//     host: defaultDeviceHost,
//     wsUrl: `http://${defaultWsHost}:3000`,
//     port: item,
//     moveCenter,
//     keyMap
//   }
// }))
const devices = ref([])
const firstInfoDevice = computed(() => {
  const info = devices.value?.find(item => item.key === firstInfo.value)

  return info
})

const activeKeys = ref(devices.value.map(o => o.key))
const recording = ref(false)
const records = ref([])
const [recordsTableData, setRecordsTableData] = useLocalStorageState('records', {
  defaultValue: []
})
const startRecordDate = ref(0)
const settingDrawerVisible = ref(false)
const settingForm = ref({})

let loopExecuting = false
let currentJob = {}

const cronDescription = computed(() => {
  if (!settingForm.value.cron) {
    return ''
  }

  try {
    return cronstrue.toString(settingForm.value.cron, { locale: 'zh_CN' });
  } catch (e) {
    // console.error(e)
    return '错误格式'
  }
})

const cronNextTime = computed(() => {
  if (!settingForm.value.cron) {
    return ''
  }

  try {
    const interval = CronExpressionParser.parse(settingForm.value.cron);
    return dayjs(interval.next().toISOString()).format('YYYY-MM-DD HH:mm:ss')
  } catch (e) {
    // console.error(e)
    return '错误格式'
  }
})

const hasStarted = computed(() => {
  return toValue(recordsTableData).some(item => item.startRun && item.type === 1)
})

let _timeoutTimer = null

function onSetRow(row) {
  const form = toValue(settingForm)
  const arr = toValue(recordsTableData)
  const index = arr.findIndex(item => item.key === form.key)

  const newArr = arr.map(item => {
    if (item.key === form.key) {
      return { ...form }
    }

    return item
  })

  setRecordsTableData(newArr)

  settingDrawerVisible.value = false
}

function onSetting(row) {
  settingForm.value = { ...row }
  settingDrawerVisible.value = true
}

function onStartRecording() {
  recording.value = true
  startRecordDate.value = Date.now()
  records.value = []
}

async function onImport() {
  const file = await pickFile()
  console.log(file);
  const fr = new FileReader()
  fr.onload = function (e) {
    console.log(fr.result);
    const jd = JSON.parse(fr.result)
    jd.key = Date.now()

    setRecordsTableData([...recordsTableData.value, jd])
  }
  fr.readAsText(file, 'utf8')
}

function onStopRecording() {
  recording.value = false

  if (records.value?.length > 0) {
    const key = Date.now()
    const newo = {
      key,
      name: `录制${key}`,
      type: 1, // 1 普通； 2 定时任务
      isLoop: true,
      loopDelay: 0,
      cron: '0 19 * * *',
      data: [...records.value]
    }
    setRecordsTableData([...recordsTableData.value, newo])
    records.value = []
  }
}

function onChangeImgPath(item, p) {
  item.imgPath = p
}

function onChangeActive(active) {
  if (firstInfo.value === active) {
    firstInfo.value = ''
    return
  }

  firstInfo.value = active

  // if (!info) {
  //   firstInfo.value = undefined
  //   return
  // }
  // firstInfo.value = {
  //   key: info.key,
  //   type: info.type,
  //   host: info.host,
  //   port: info.port,
  // }
}

function onChangeIsLoop(row, index, evt) {
  row.isLoop = !row.isLoop
}

function onStartRun(row, index) {
  row.startRun = true


  if (row.type === 1) {
    // 普通任务
    startRunFn(row)
  } else if (row.type === 2 && row.cron) {
    // 定时任务
    // startRunFn(row)
    startRunCron(row)
  } else {
    ElMessage.error('无效的类型，或没有cron表达式')
    row.startRun = false
  }
}

function loopRunData(arr, done) {
  if (loopExecuting) {
    console.error('有正在运行的脚本，退出该脚本')
    done()
    return
  }

  const one = arr.shift()

  if (!one) {
    loopExecuting = false
    done()
    return
  }

  loopExecuting = true

  _timeoutTimer = setTimeout(() => {
    onCommand(one.commands)
    loopExecuting = false
    loopRunData(arr, done)
  }, one.time);
}

function stopJob(key, name) {
  if (currentJob[key]) {
    currentJob[key]?.stop()
    currentJob[key] = null
    console.log('停止定时任务: ', name);
  }
}

function startRunCron(row) {
  stopJob(row.key, row.name)

  try {
    console.log('开始定时任务：', row.name)
    const interval = CronExpressionParser.parse(row.cron);
    const s1 = dayjs(interval.next().toISOString()).format('YYYY-MM-DD HH:mm:ss')
    console.log('下次执行时间：', s1)
    currentJob[row.key] = new Cron(row.cron, () => {
      console.log('执行定时任务: ', row.name);
      loopRunData([...row.data], () => {
        console.log('结束执行任务：', row.name)
        const interval = CronExpressionParser.parse(row.cron);
        const s1 = dayjs(interval.next().toISOString()).format('YYYY-MM-DD HH:mm:ss')
        console.log('下次任务执行时间：', s1)
      })
    });
  } catch (e) {
    console.log('开始定时任务报错：', row.name)
    console.error(e)
    onStopRun(row, index)
  }
}

function startRunFn(row) {
  loopRunData([...row.data], () => {
    if (row.isLoop && row.startRun) {
      clearTimeout(_timeoutTimer)

      _timeoutTimer = setTimeout(() => {
        startRunFn(row)
      }, row.loopDelay * 1000);
    }
  })
}

function onStopRun(row, index) {
  row.startRun = false
  clearTimeout(_timeoutTimer)
  loopExecuting = false
  stopJob(row.key, row.name)
}

function onDownloadJson(row, index) {
  downloadJson(JSON.stringify(row, null, 2), row.name + '.json', 'application/json')
}

function onRemoveRow(row, index) {
  const arr = toValue(recordsTableData)
  const newArr = []

  arr.forEach((n, i) => {
    if (i === index) {
      return
    }
    newArr.push(n)
  })
  setRecordsTableData(newArr)
}
function onCommand(commands) {
  const allModel = commands?.allModel
  const activeKey = commands?.activeKey

  if (recording.value) {
    records.value.push({ time: Date.now() - startRecordDate.value, commands })
    startRecordDate.value = Date.now()
  }

  if (allModel) {
    adbDevices.value?.forEach(adbRef => {
      adbRef.sendCommandWithCommands(commands)
    })
  } else {
    adbDevices.value?.forEach(adbRef => {
      if (adbRef.key === activeKey) {
        adbRef.sendCommandWithCommands(commands)
      }
    })
  }
}

onMounted(() => {
  fetch('http://localhost:3000/get-devices', { mode: 'cors' }).then(res => res.json()).then(list => {
    console.log('list', list);

    devices.value = list.filter(item => item.id.indexOf('emulator-') < 0 && item.state === 'device').map(item => {
      return {
        key: item.id,
        deviceId: item.id,
        type: 'device',
        wsUrl: `http://${defaultWsHost}:3000`,
        moveCenter,
        keyMap
      }
    })
  })
})
</script>
<style lang="scss" scoped></style>